package Server;
import java.io.BufferedReader;
import java.io.FileReader;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Vector;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import Server.map.Box;
import Server.map.FightHappened;
import Server.map.Map;
import Server.players.Dino;
import Server.players.Player;
import Server.players.PlayerList;

import common.ClientInterface;
import common.FileLogger;
import common.Ranking;
import common.ServerInterface;
import common.UserMap;
import common.exception.*;

/** @author: Flavio Ferrara
 * Singleton Game
 */

/*Tutti i metodi derivati da ServerInterface devono lanciare RemoteException
 * per RMI
 * 
 * Alcune procedure devono essere transazionali
 * Modificare le eccezioni per usare IllegalArgumentException
 * Da completare:
 * - Mappa : generateMap
 * - Player : costruttore copia
 * - Game :  nextTurn (notifica ai client)
 * - mainServer
 */
public class Game  extends UnicastRemoteObject implements ServerInterface
{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private static Game game;

	private PlayerList players;
	private Map map;

	transient private FileLogger logger;
	transient private ClientInterface myClient;
	transient private int idCounter;
	transient private ArrayList<Integer> idDinosMoved;
	transient private ArrayList<Integer> idDinosActioned;

	transient private ScheduledThreadPoolExecutor stpe;
	transient private ScheduledFuture<?> turnHandler;

	private Connection db;

	class TurnHandler implements Runnable {
		@Override
		public void run() {
			nextTurn();
		}
	}

	private Game(boolean restoreFromDB) throws RemoteException{
		//super() inserito per RMI
		super();
		this.players = new PlayerList();
		this.map = new Map(null);
		this.logger = new FileLogger();
		this.myClient = null;
		this.idCounter = 1;
		this.idDinosActioned = new ArrayList<Integer>();
		this.idDinosMoved = new ArrayList<Integer>();
		this.stpe = new ScheduledThreadPoolExecutor(1);
		this.turnHandler = null;

		try{
			Class.forName("org.sqlite.JDBC");
			this.db = DriverManager.getConnection("jdbc:sqlite:isola");

			if (restoreFromDB)
			{
				Statement stat = this.db.createStatement();
				ResultSet map = stat.executeQuery("SELECT * FROM map");
				ResultSet foods = stat.executeQuery("SELECT * FROM food");

				char[][] data = new char[Map.SIZE][];
				for (@SuppressWarnings("unused") char[] row : data)
					row = new char[Map.SIZE];

				while (map.next()){
					int x = map.getInt("x");
					int y = map.getInt("y");
					data[x][y] = (map.getBoolean("water") ? 'a' : 't'); 
				}
				this.map = new Map(data);

				while (foods.next()){
					int x = foods.getInt("x");
					int y = foods.getInt("y");
					String type = foods.getString("type");
					int energy = foods.getInt("energy");
					int maxEnergy = foods.getInt("maxEnergy");
					Box b = this.map.get(x, y);
					if (type.equals("e")){
						b.putVegetable(energy, maxEnergy);
					} else if (type.equals("c")){
						b.putMeat(energy, maxEnergy);
					}
				}
			} else {
				Statement stat = this.db.createStatement();
				//				stat.executeUpdate("DROP TABLE IF EXISTS map");
				stat.executeUpdate("DROP TABLE IF EXISTS food");
				stat.executeUpdate("DROP TABLE IF EXISTS players");
				stat.executeUpdate("DROP TABLE IF EXISTS species");
				stat.executeUpdate("DROP TABLE IF EXISTS dinos");
				stat.executeUpdate("DROP TABLE IF EXISTS rank");
				stat.executeUpdate("CREATE TABLE food(x INTEGER , y INTEGER, type CHAR(1), maxEnergy INTEGER, energy INTEGER," +
				"PRIMARY KEY(x, y), FOREIGN KEY(x) REFERENCES map(x), FOREIGN KEY(y) REFERENCES map(y))");
				stat.executeUpdate("CREATE TABLE players(name VARCHAR(10) PRIMARY KEY, password VARCHAR(10), logged BOOLEAN, playing BOOLEAN)");
				stat.executeUpdate("CREATE TABLE species(name VARCHAR(10) PRIMARY KEY, type CHAR(1), age INTEGER," +
				"owner VARCHAR(10) REFERENCES players(name))");
				stat.executeUpdate("CREATE TABLE dinos(ID INTEGER PRIMARY KEY, specie VARCHAR(10) REFERENCES species(name)," +
						"x INTEGER REFERENCES map(x), y INTEGER REFERENCES map(y)," +
				"dimension INTEGER, energy INTEGER, age INTEGER, ageLimit INTEGER)");
				stat.executeUpdate("CREATE TABLE rank(username VARCHAR(10) REFERENCES players(name)," +
				"specie VARCHAR(10) REFERENCES species(name), score INTEGER, state CHAR)");

				// se esiste gia viene lanciata un'eccezione e storeMap non viene chiamata
				stat.executeUpdate("CREATE TABLE map(x INTEGER, y INTEGER, water BOOLEAN, PRIMARY KEY(x, y))");
				this.storeMap();
			}
		} catch (ClassNotFoundException e){
			this.logger.logError("NIENTE DB");
		} catch (SQLException e){
			this.logger.logError("Errore SQL: "+ e.getMessage());
		} finally {
			try{
				this.db.close();
			} catch (SQLException e){

			}
		}



	}

	public static Game getGame() throws RemoteException{ 
		if (game == null)
			game = new Game(false);
		return game;
	}

	public static Game getGame(boolean restoreFromDb) throws RemoteException{
		if (game == null)
			game = new Game(restoreFromDb);

		return game;
	}

	/**
	 * Accetta un file organizzato come sequenza di caratteri, separati da spazio, tab o nulla
	 * @param filename
	 */
	public void useMapData(String filename){
		char[][] data = new char[Map.SIZE][Map.SIZE];
		try{
			BufferedReader reader = new BufferedReader(new FileReader(filename));
			String line = reader.readLine();
			for (int i = 0;line != null;i++){
				data[i] = line.replaceAll("\\s", "").toCharArray();
				line = reader.readLine();
			}
		} catch (Exception e){
			throw new RuntimeException(e);
		}

		this.map = new Map(data);
	}

	public void restoreFromDB(){

	}

	public void setClient(ClientInterface client){
		this.myClient = client;
	}

	/* helper */
	private Player validateToken(int token) throws InvalidTokenException{
		Player p = this.players.get(token);
		if (p == null){
			this.logger.logError("Token non valido: "+token);
			throw new InvalidTokenException();
		}
		return p;
	}

	private int getNextIdDino() {
		int nextId = this.idCounter;
		this.idCounter++;
		return nextId;
	}

	private void storeMap(){
		try{
			Class.forName("org.sqlite.JDBC");
			this.db = DriverManager.getConnection("jdbc:sqlite:isola");
			PreparedStatement query = this.db.prepareStatement("INSERT INTO map(x, y, water) VALUES (?, ?, ?)");
			PreparedStatement query2 = this.db.prepareStatement("INSERT INTO food(x, y, type, maxEnergy, energy) VALUES (?, ?, ?, ?, ?)");
			for (int i = 0;i < Map.SIZE;i++){
				for (int j = 0;j < Map.SIZE;j++){
					Box b = this.map.get(i, j);
					query.setInt(1, i);
					query.setInt(2, j);
					query.setBoolean(3, b.isWater());
					query.executeUpdate();

					if (b.isMeatBox()){
						query2.setInt(1, i);
						query2.setInt(2, j);
						query2.setString(3, "c");
						query2.setInt(4, b.getMaxFoodEnergy());
						query2.setInt(5, b.getFoodEnergy());
						query2.executeUpdate();
					} else if (b.isVegetableBox()){
						query2.setInt(1, i);
						query2.setInt(2, j);
						query2.setString(3, "e");
						query2.setInt(4, b.getMaxFoodEnergy());
						query2.setInt(5, b.getFoodEnergy());
						query2.executeUpdate();
					}
				}
			}
		} catch (ClassNotFoundException e){
			this.logger.logError("Niente DB");
		} catch(SQLException e){
			this.logger.logError("ERRORE SQL: "+ e.getMessage());
		} finally {
			try{
				this.db.close();
			} catch(SQLException e){
				this.logger.logError("ERRORE SQL: "+ e.getMessage());
			} 
		}
	}

	/* ######## RECUPERO INFO #############
	 * 
	 */
	@Override
	public Integer[] getDinoList(int token) throws InvalidTokenException,
	NotInMatchException {
		Player asker = this.validateToken(token);

		if (! this.players.isInGame(asker)){
			this.logger.logError("Il giocatore "+asker+" non è in gioco");
			throw new NotInMatchException();
		}
		return asker.getDinoList();
	}

	@Override
	public String[] getPlayers(int token) throws InvalidTokenException {
		this.validateToken(token);

		return this.players.getInGamePlayers();
	}

	@Override
	public Ranking getRanking(int token) throws InvalidTokenException {
		this.validateToken(token);

		return Ranking.getRanking();
	}

	@Override
	public UserMap getMap(int token) throws InvalidTokenException,
	NotInMatchException {

		Player asker = this.validateToken(token);
		if (! this.players.isInGame(asker)){
			this.logger.logError("Il giocatore "+asker+" non è in gioco");
			throw new NotInMatchException();
		}

		return this.map.getUserMap(asker);
	}

	@Override
	public Vector<Object> getDinoStat(int token, int idDino)
	throws InvalidTokenException, InvalidIdException,
	NotInMatchException {

		Player asker = this.validateToken(token);
		if (! this.players.isInGame(asker)){
			this.logger.logError("Il giocatore "+asker+" non è in gioco");
			throw new NotInMatchException();
		}

		Vector<Object> dinoStat = new Vector<Object>();
		Dino dino = this.players.getDino(idDino);
		if (dino == null){
			this.logger.logError("IDINO non valido:"+idDino);
 
		}

		try{
			Player owner = this.players.getOwner(dino);

			dinoStat.add(owner.getName());
			dinoStat.add(owner.getSpeciesName());
			dinoStat.add(owner.getSpeciesType());
			dinoStat.add(dino.getX());
			dinoStat.add(dino.getY());
			dinoStat.add(dino.getDimension());

			if (asker.equals(owner)){
				dinoStat.add(dino.getEnergy());
				dinoStat.add(dino.getAge());
			}
		} catch (Exception e){
			this.logger.logError("Errore generico: "+e.getMessage());
			throw new RuntimeException(e);
		}

		return dinoStat;
	}

	@Override
	public UserMap getDinoVisual(int token, int idDino)
	throws InvalidTokenException, InvalidIdException,
	NotInMatchException {

		Player asker = this.validateToken(token);
		Dino dino = this.players.getDino(idDino);
		if (dino == null){
			this.logger.logError("Il dino con ID "+idDino+" non esiste");
			throw new InvalidIdException();
		}
		if (! asker.owns(dino)){
			this.logger.logError("Il dino con ID "+idDino+" non appartiene al giocatore "+asker);
			throw new InvalidIdException();
		}

		return this.map.getDinoVisual(asker, dino);
	}


	/* ####################### COMANDI GESTIONE ##################
	 * 
	 */
	public int login(String username, String psw) throws FailedAuthenticationException{
		int token = this.players.login(username, psw);
		if (token < 0){
			this.logger.logError("Autenticazione fallita per l'utente "+username);
			throw new FailedAuthenticationException();
		}

		try{
			this.db = DriverManager.getConnection("jdbc:sqlite:isola");
			Statement stat = this.db.createStatement();
			stat.executeUpdate("UPDATE players SET logged = 'true' WHERE name = '"+username+"'");
		} catch(SQLException e){
			this.logger.logError("ERRORE SQL: "+ e.getMessage());
		} finally {
			try{
				this.db.close();
			} catch (SQLException e){
				this.logger.logError("ERRORE SQL: "+ e.getMessage());
			}
		}

		return token;
	}

	@Override
	public void newSpecies(int token, String name, char type)
	throws InvalidTokenException, BusyNameException {

		Player asker = this.validateToken(token);
		if (this.players.existsSpecies(name)){
			this.logger.logError("Nome già occupato: "+name);
			throw new BusyNameException();
		} 
		// DA DEFINIRE: se il carattere non è corretto non viene creato nulla
		if (type == 'c' || type == 'e'){
			Dino first = this.players.newSpecies(asker, name, type, this.getNextIdDino());
			Box b = this.map.getRandomFreeBox();
			this.map.putDino(first, b);
			first.place(b);

			Ranking rank = Ranking.getRanking();
			rank.putPlayer(0, asker.getName(), name);

			try{
				this.db = DriverManager.getConnection("jdbc:sqlite:isola");
				PreparedStatement stat = this.db.prepareStatement("INSERT INTO species VALUES(?, ?, ?, ?)");
				stat.setString(1, name);
				stat.setString(2, Character.toString(type));
				stat.setInt(3, asker.getSpeciesAge());
				stat.setString(4, asker.getName());

				stat.executeUpdate();

				stat = this.db.prepareStatement("INSERT INTO dinos(ID, specie, x, y, dimension, energy, age, ageLimit) VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
				stat.setInt(1, first.getId());
				stat.setString(2, name);
				stat.setInt(3, first.getX());
				stat.setInt(4, first.getY());
				stat.setInt(5, first.getDimension());
				stat.setInt(5, first.getEnergy());
				stat.setInt(5, first.getAge());
				stat.setInt(5, first.getAgeLimit());
				stat.executeUpdate();
			} catch(SQLException e){
				this.logger.logError("ERRORE SQL: "+ e.getMessage());
			} finally {
				try{
					this.db.close();
				} catch (SQLException e){
					this.logger.logError("ERRORE SQL: "+ e.getMessage());
				}
			}
		}
	}

	@Override
	public void newUser(String username, String psw) throws BusyNameException {
		if (this.players.exists(username)){
			this.logger.logError("Nome già occupato: "+username);
			throw new BusyNameException();
		}

		Player newPlayer = new Player(username, psw);
		this.players.add(newPlayer);

		try{
			this.db = DriverManager.getConnection("jdbc:sqlite:isola");
			PreparedStatement stat = this.db.prepareStatement("INSERT INTO players(name, password, logged, playing) VALUES (?,?,?,?)");
			stat.setString(1, username);
			stat.setString(2, psw);
			stat.setBoolean(3, false);
			stat.setBoolean(3, false);

			stat.executeUpdate();
		} catch(SQLException e){
			this.logger.logError("ERRORE SQL: "+ e.getMessage());
		} finally {
			try{
				this.db.close();
			} catch (SQLException e){
				this.logger.logError("ERRORE SQL: "+ e.getMessage());
			}
		}
	}

	@Override
	public void inGame(int token) throws InvalidTokenException, FullMatchException {
		Player asker = this.validateToken(token);

		if (asker.getSpeciesName().equals(""))
			throw new IllegalArgumentException();

		if (! this.players.inGame(asker)){ // ritorna false se la partita è piena
			this.logger.logError("Partita piena, impossibile accedere per: "+token);
			throw new FullMatchException();
		}

		Ranking rank = Ranking.getRanking();
		rank.setPlaying(asker.getName());
		
		if (this.players.getInGamePlayers().length == 1 && this.myClient != null){
			try {
				this.myClient.notifyTurn(asker.getName());
			} catch (RemoteException e) {
				this.logger.logError("ERRORE RMI: "+ e.getMessage());
			}
		}

		try{
			this.db = DriverManager.getConnection("jdbc:sqlite:isola");
			Statement stat = this.db.createStatement();
			stat.executeUpdate("UPDATE players SET playing = 'true' WHERE name = '"+asker.getName()+"'");
		} catch(SQLException e){
			this.logger.logError("ERRORE SQL: "+ e.getMessage());
		} finally {
			try{
				this.db.close();
			} catch (SQLException e){
				this.logger.logError("ERRORE SQL: "+ e.getMessage());
			}
		}
	}

	@Override
	public void outGame(int token) throws InvalidTokenException {
		Player asker = this.validateToken(token);
		this.players.outGame(asker);
		
		if (this.players.getInGamePlayers().length == 0){
			if (this.turnHandler != null)
				this.turnHandler.cancel(true);
		}

		try{
			this.db = DriverManager.getConnection("jdbc:sqlite:isola");
			Statement stat = this.db.createStatement();
			stat.executeUpdate("UPDATE players SET playing = 'false' WHERE name = '"+asker.getName()+"'");
		} catch(SQLException e){
			this.logger.logError("ERRORE SQL: "+ e.getMessage());
		} finally {
			try{
				this.db.close();
			} catch (SQLException e){
				this.logger.logError("ERRORE SQL: "+ e.getMessage());
			}
		}
	}

	@Override
	public void logout(int token) throws InvalidTokenException {
		Player asker = this.validateToken(token);
		if (this.players.isInGame(asker))
			this.players.outGame(asker);
		
		this.players.logout(asker);

		try{
			this.db = DriverManager.getConnection("jdbc:sqlite:isola");
			Statement stat = this.db.createStatement();
			stat.executeUpdate("UPDATE players SET logged = 'false' WHERE name = '"+asker.getName()+"'");
		} catch(SQLException e){
			this.logger.logError("ERRORE SQL: "+ e.getMessage());
		} finally {
			try{
				this.db.close();
			} catch (SQLException e){
				this.logger.logError("ERRORE SQL: "+ e.getMessage());
			}
		}
	}



	/* ######## GESTIONE TURNI ################# */



	private void nextTurn(){
		this.map.updateFood();
		this.idDinosActioned.clear();
		this.idDinosMoved.clear();

		Ranking rank = Ranking.getRanking();
		
		try{
			this.players.updateAges();
		} catch (ExtinctionException e){
			rank = Ranking.getRanking();
			rank.setExtinct(this.players.getCurrentPlayer().getName());
			this.players.extinct(this.players.getCurrentPlayer());
		}


		for (int i = 0;i < PlayerList.MAX_PLAYERS;i++){
			Player p = this.players.get(i);
			if (p != null){
				int score = 0;
				for (Integer id : p.getDinoList()){
					score += this.players.getDino(id).getDimension() + 1;
				}
				rank.updateScore(p.getName(), score);
			}

		}

		Player nextCurrent = this.players.nextCurrentPlayer();
		// Gestione uova
		int eggs = nextCurrent.getEgg();
		if (eggs != 0){
			Dino d = nextCurrent.newDino(eggs);
			Box position = this.map.getRandomFreeBox();
			this.map.putDino(d, position);
			d.place(position);
		}
		//
		try{
			if (this.myClient != null)
				this.myClient.notifyTurn(nextCurrent.getName());
		} catch (RemoteException e){
			this.logger.logError("ERRORE RMI: "+ e.getMessage());
		}
		
		// riavvio il timeout
		if (this.turnHandler != null)
			this.turnHandler.cancel(true);
		this.turnHandler = this.stpe.schedule(new TurnHandler(), 2, TimeUnit.MINUTES);
	}

	@Override
	synchronized public void skipTurn(int token) throws InvalidTokenException,
	NotInMatchException, GameException {
		Player asker = this.validateToken(token);
		if (! this.players.isInGame(asker)){
			this.logger.logError("Il giocatore "+asker+" non è in gioco");
			throw new NotInMatchException();
		}
		if (! asker.equals(this.players.getCurrentPlayer())){
			this.logger.logError("Non è il tuo turno: "+asker);
			throw new NotYourTurnException();
		}

		this.nextTurn();
	}

	@Override
	synchronized public void useTurn(int token) throws InvalidTokenException,
	NotInMatchException, GameException {
		Player asker = this.validateToken(token);
		if (! this.players.isInGame(asker)){
			this.logger.logError("Il giocatore "+asker+" non è in gioco");
			throw new NotInMatchException();
		}
		if (! asker.equals(this.players.getCurrentPlayer())){
			this.logger.logError("Non è il tuo turno: "+asker);
			throw new NotYourTurnException();
		}

		if (this.turnHandler != null)
			this.turnHandler.cancel(true);
		this.turnHandler = this.stpe.schedule(new TurnHandler(), 2, TimeUnit.MINUTES);
		System.out.println("Turno usato: "+asker.getName());
	}





	/* ########### AZIONI DI GIOCO ##########
	 * 
	 */
	@Override
	public int egg(int token, int idDino) throws InvalidTokenException, NotInMatchException, InvalidIdException, 
	GameException {

		Player asker = this.validateToken(token);
		if (! this.players.isInGame(asker)){
			this.logger.logError("Il giocatore "+asker+" non è in gioco");
			throw new NotInMatchException();
		}
		if (! asker.equals(this.players.getCurrentPlayer())){
			this.logger.logError("Non è il tuo turno: "+asker);
			throw new NotYourTurnException();
		}
		if (asker.maxNumberOfDinoReached()){
			this.logger.logError("Raggiunto numero massimo dinosauri: "+asker);
			throw new MaxNumberOfDinoException();
		}
		if (this.idDinosActioned.contains(idDino)){
			this.logger.logError("Gia azionato il dino :" + idDino);
			throw new InvalidIdException();
		}

		Dino father = asker.getDino(idDino);
		try{
			if (! father.egg()){ // morto
				asker.removeDino(father);
				this.map.removeDino(father);
				this.logger.logInfo("Dinosauro morto di fame: " + idDino);
				
				if (asker.getDinoList().length == 0){
					Ranking rank = Ranking.getRanking();
					rank.setExtinct(this.players.getCurrentPlayer().getName());
					this.players.extinct(this.players.getCurrentPlayer());
					throw new ExtinctionException();
				}
				throw new StarvedException();
			}
		} catch (NullPointerException e){
			this.logger.logError("IDINO non valido:"+idDino);
			throw new InvalidIdException();
		}

		int idChild = this.getNextIdDino();
		try{
			asker.newEgg(idChild);
		} catch (Exception f){
			this.logger.logError("Errore generico: "+f.getMessage());
			throw new RuntimeException(f);
		}
		
		this.idDinosActioned.add(idDino);
		return idChild;
	}

	@Override
	public void growDino(int token, int idDino) throws InvalidTokenException, NotInMatchException, InvalidIdException, 
	GameException {

		Player asker = this.validateToken(token);
		if (! this.players.isInGame(asker)){
			this.logger.logError("Il giocatore "+asker+" non è in gioco");
			throw new NotInMatchException();
		}
		if (! asker.equals(this.players.getCurrentPlayer())){
			this.logger.logError("Non è il tuo turno: "+asker);
			throw new NotYourTurnException();
		}
		if (this.idDinosActioned.contains(idDino)){
			this.logger.logError("Gia azionato il dino :" + idDino);
			throw new InvalidIdException();
		}

		Dino smallDino = asker.getDino(idDino);
		try{
			if (! smallDino.grow()){ // morto
				asker.removeDino(smallDino);
				this.map.removeDino(smallDino);
				this.logger.logInfo("Dinosauro morto di fame: " + idDino);
				if (asker.getDinoList().length == 0){
					Ranking rank = Ranking.getRanking();
					rank.setExtinct(this.players.getCurrentPlayer().getName());
					this.players.extinct(this.players.getCurrentPlayer());
					throw new ExtinctionException();
				}
				
				throw new StarvedException();
			}
		} catch (NullPointerException e){
			this.logger.logError("IDINO non valido:"+idDino);
			throw new InvalidIdException();
		} 
		
		this.idDinosActioned.add(idDino);
	}


	@Override
	public char move(int token, int idDino, int[] destination)
	throws InvalidTokenException, InvalidIdException,
	NotInMatchException, GameException {

		Player asker = this.validateToken(token);
		if (! this.players.isInGame(asker)){
			this.logger.logError("Il giocatore "+asker+" non è in gioco");
			throw new NotInMatchException();
		}
		if (! asker.equals(this.players.getCurrentPlayer())){
			this.logger.logError("Non è il tuo turno: "+asker);
			throw new NotYourTurnException();
		}

		Dino movingDino = asker.getDino(idDino);
		if (movingDino == null){
			this.logger.logError("IDDINO non valido: "+idDino);
			throw new InvalidIdException();
		}
		if (this.idDinosMoved.contains(idDino)){
			this.logger.logError("Gia azionato il dino :" + idDino);
			throw new InvalidIdException();
		}

		Box boxDestionation = this.map.get(destination[0], destination[1]);
		if (this.map.reachable(movingDino, boxDestionation)){
			int distance = this.map.computeDistance(new Box(movingDino.getX(), movingDino.getY(), false), boxDestionation, movingDino);
			try{
				if (! movingDino.enoughEnergyToMove(distance)){ // morto
					asker.removeDino(movingDino);
					this.map.removeDino(movingDino);
					this.logger.logInfo("Dinosauro morto di fame: " + idDino);
					if (asker.getDinoList().length == 0){
						Ranking rank = Ranking.getRanking();
						rank.setExtinct(this.players.getCurrentPlayer().getName());
						this.players.extinct(this.players.getCurrentPlayer());
						throw new ExtinctionException();
					}

					throw new StarvedException();
				}
				// mangia anche, se possibile
				this.map.moveDino(movingDino, boxDestionation);
				movingDino.move(boxDestionation, distance);
			} catch (FightHappened e){
				// GESTIRE LA MORTE DI UN DINO
				int idLoser = Integer.parseInt(e.getMessage());
				if (idLoser == idDino){
					asker.removeDino(movingDino);
					return 'p';
				}

				movingDino.move(boxDestionation, distance);
				Dino loser = this.players.getDino(idLoser);
				this.players.getOwner(loser).removeDino(loser);
				return 'v';
			}
		} else {
			this.logger.logError("Destinazione non raggiungibile: " + destination[0] + "," + destination[1]);
			throw new InvalidDestinationException();
		}
		this.idDinosMoved.add(idDino);
		return 'x';
	}
}
